﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

using SharedGenomics.Core;

namespace SharedGenomics.Workbench.Core
{
    public class EventBroker : IEventBroker
    {
        private IDictionary<Type, ICollection<WeakReference>> _subscribers;

        public EventBroker()
        {
            this._subscribers = new Dictionary<Type, ICollection<WeakReference>>();
        }

        #region IEventBroker Members

        public void Publish<T>(T message) where T : class
        {
            List<WeakReference> deadReferences = new List<WeakReference>();
            var subscriberReferences = this.GetSubscribersForEvent(typeof(T));

            foreach (WeakReference reference in subscriberReferences)
            {
                if (reference.IsAlive)
                {
                    //possible race condition if target is gc'd before being accessed?
                    IEventSubscriber<T> subscriber = (IEventSubscriber<T>)reference.Target;
                    subscriber.Receive(message);
                }
                else
                {
                    deadReferences.Add(reference);
                }
            }

            //remove all dead references
            foreach (var deadRef in deadReferences)
            {
                subscriberReferences.Remove(deadRef);
            }
        }

        public void Subscribe<T>(IEventSubscriber<T> subscriber) where T : class
        {
            this.Subscribe(typeof(T), subscriber);
        }

        public void Subscribe(Type eventType, object subscriber)
        {
            eventType.ThrowIfNull("eventType");
            subscriber.ThrowIfNull("subscriber");
            ExceptionHelper.ThrowIf<ArgumentException>(!eventType.IsClass, "Events must be reference types");

            var eventSubscribers = this.GetSubscribersForEvent(eventType);

            //add to list of subscribers if not already present
            if (!eventSubscribers.Any(wr => Object.ReferenceEquals(subscriber, wr.Target)))
            {
                WeakReference subscriberRef = new WeakReference(subscriber, false);
                eventSubscribers.Add(subscriberRef);
            }
        }

        public void SubscribeAll(object subscriber)
        {
            subscriber.ThrowIfNull("subscriber");

            foreach (Type eventType in this.GetEventMessageTypesForSubscriber(subscriber))
            {
                this.Subscribe(eventType, subscriber);
            }
        }

        public void Unsubscribe<T>(IEventSubscriber<T> subscriber) where T : class
        {
            subscriber.ThrowIfNull("subscriber");
            this.Unsubscribe(typeof(T), subscriber);
        }

        private void Unsubscribe(Type eventType, object subscriber)
        {
            Debug.Assert(eventType != null);
            Debug.Assert(subscriber != null);

            var eventSubscribers = this.GetSubscribersForEvent(eventType);
            WeakReference subRef = eventSubscribers.Where(wr => Object.ReferenceEquals(wr.Target, subscriber)).FirstOrDefault();

            if (subRef != null)
            {
                eventSubscribers.Remove(subRef);
            }
        }

        public void UnsubscribeAll(object subscriber)
        {
            subscriber.ThrowIfNull("subscriber");

            var eventTypes = this.GetEventMessageTypesForSubscriber(subscriber);

            foreach (Type eventType in eventTypes)
            {
                this.Unsubscribe(eventType, subscriber);
            }
        }

        #endregion

        //NOTE: this is protected for test purposes only and probably isn't very useful for subclasses in general...
        protected virtual ICollection<WeakReference> GetSubscribersForEvent(Type eventType)
        {
            Debug.Assert(eventType != null);

            if (!this._subscribers.ContainsKey(eventType))
            {
                this._subscribers[eventType] = new HashSet<WeakReference>();
            }

            return this._subscribers[eventType];
        }

        private IEnumerable<Type> GetEventMessageTypesForSubscriber(object subscriber)
        {
            Debug.Assert(subscriber != null);

            return from ifaceType in subscriber.GetType().GetInterfaces()
                   let genericParams = ifaceType.GetGenericArguments()
                   where genericParams.Length == 1 && typeof(IEventSubscriber<>).MakeGenericType(genericParams[0]).IsAssignableFrom(ifaceType)
                   select genericParams[0];
        }
    }
}
